<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>RSA加密演示</title>
    <!-- 添加JSEncrypt库 -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jsencrypt/3.3.2/jsencrypt.min.js"></script>
    <!-- 添加forge库，用于OAEP填充支持 -->
    <script src="https://cdn.jsdelivr.net/npm/node-forge@1.0.0/dist/forge.min.js"></script>
    <style>
      body {
        font-family: "Microsoft YaHei", Arial, sans-serif;
        line-height: 1.6;
        color: #333;
        max-width: 800px;
        margin: 0 auto;
        padding: 20px;
      }
      h1 {
        text-align: center;
        color: #2c3e50;
      }
      .container {
        background-color: #f8f9fa;
        border-radius: 5px;
        padding: 20px;
        box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
      }
      .form-group {
        margin-bottom: 15px;
      }
      label {
        display: block;
        margin-bottom: 5px;
        font-weight: bold;
      }
      textarea,
      input {
        width: 100%;
        padding: 10px;
        border: 1px solid #ddd;
        border-radius: 4px;
        box-sizing: border-box;
        font-family: monospace;
      }
      button {
        background-color: #4caf50;
        color: white;
        border: none;
        padding: 10px 15px;
        border-radius: 4px;
        cursor: pointer;
        margin-right: 5px;
      }
      button:hover {
        background-color: #45a049;
      }
      .result {
        margin-top: 20px;
        padding: 15px;
        background-color: #e9f5e9;
        border-radius: 4px;
        white-space: pre-wrap;
        word-break: break-all;
      }
      .error {
        background-color: #f8d7da;
        color: #721c24;
      }
      .loading {
        margin: 20px 0;
        text-align: center;
        color: #666;
      }
      .hide {
        display: none;
      }
      .steps {
        margin-bottom: 30px;
      }
      .step {
        padding: 10px;
        margin-bottom: 10px;
        border-left: 3px solid #4caf50;
        background-color: #f1f1f1;
      }
      .radio-group {
        margin: 10px 0;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <h1>RSA加密演示</h1>

      <div class="steps">
        <div class="step">
          <strong>步骤1:</strong>
          获取服务器公钥
        </div>
        <div class="step">
          <strong>步骤2:</strong>
          输入需要加密的内容
        </div>
        <div class="step">
          <strong>步骤3:</strong>
          使用公钥加密内容
        </div>
        <div class="step">
          <strong>步骤4:</strong>
          发送加密内容到服务器进行解密验证
        </div>
      </div>

      <div class="form-group">
        <button id="fetchPublicKeyBtn">获取服务器公钥</button>
        <div id="loadingKey" class="loading hide">正在获取公钥...</div>
      </div>

      <div class="form-group">
        <label for="publicKey">服务器公钥:</label>
        <textarea id="publicKey" rows="7" readonly></textarea>
      </div>

      <div class="form-group">
        <label for="originalData">要加密的内容:</label>
        <textarea
          id="originalData"
          rows="3"
          placeholder="输入需要加密的内容..."
        ></textarea>
      </div>

      <div class="form-group">
        <label>加密方式：</label>
        <div class="radio-group">
          <input type="radio" id="jsencrypt" name="encryptMethod" value="jsencrypt" checked>
          <label for="jsencrypt">JSEncrypt (适用于短文本)</label>
          <br>
          <input type="radio" id="forge" name="encryptMethod" value="forge">
          <label for="forge">Forge (支持OAEP填充)</label>
        </div>
      </div>

      <div class="form-group">
        <button id="encryptBtn" disabled>使用公钥加密</button>
      </div>

      <div class="form-group">
        <label for="encryptedData">加密后的内容:</label>
        <textarea id="encryptedData" rows="5" readonly></textarea>
      </div>

      <div class="form-group">
        <button id="sendToServerBtn" disabled>发送到服务器解密</button>
        <div id="loadingSend" class="loading hide">正在发送到服务器解密...</div>
      </div>

      <div id="result" class="result hide"></div>
    </div>

    <script>
      // 功能函数
      async function fetchPublicKey() {
        document.getElementById("loadingKey").classList.remove("hide")
        try {
          const response = await fetch("/api/public-key")
          const data = await response.json()

          if (data.publicKey) {
            document.getElementById("publicKey").value = data.publicKey
            document.getElementById("encryptBtn").disabled = false
            showMessage("公钥获取成功!", false)
          } else {
            showMessage("获取公钥失败: 服务器未返回有效公钥", true)
          }
        } catch (error) {
          showMessage("获取公钥失败: " + error.message, true)
        } finally {
          document.getElementById("loadingKey").classList.add("hide")
        }
      }

      async function encryptData() {
        const publicKey = document.getElementById("publicKey").value
        const originalData = document.getElementById("originalData").value
        const encryptMethod = document.querySelector('input[name="encryptMethod"]:checked').value

        if (!publicKey || !originalData) {
          showMessage("请确保已获取公钥并输入要加密的内容", true)
          return
        }

        try {
          let encrypted
          if (encryptMethod === "forge") {
            // 使用Forge库进行OAEP填充加密
            encrypted = encryptWithForge(publicKey, originalData)
          } else {
            // 使用JSEncrypt库进行加密
            encrypted = encryptWithJSEncrypt(publicKey, originalData)
          }
          
          document.getElementById("encryptedData").value = encrypted
          document.getElementById("sendToServerBtn").disabled = false
          showMessage(`使用${encryptMethod === "forge" ? "Forge (OAEP)" : "JSEncrypt"}加密成功!`, false)
        } catch (error) {
          showMessage("加密失败: " + error.message, true)
        }
      }

      // 使用JSEncrypt库进行RSA加密
      function encryptWithJSEncrypt(publicKey, data) {
        // 创建JSEncrypt实例
        const encrypt = new JSEncrypt()
        // 设置公钥
        encrypt.setPublicKey(publicKey)
        
        try {
          // 对于JSEncrypt，我们总是先将数据进行UTF-8编码和Base64转换
          // 这样可以确保处理中文等非ASCII字符
          const encoder = new TextEncoder()
          const bytes = encoder.encode(data)
          
          // 将字节数组转换为字符串，以便进行Base64编码
          let binaryString = ""
          for (let i = 0; i < bytes.length; i++) {
            binaryString += String.fromCharCode(bytes[i])
          }
          
          // 进行Base64编码
          const base64 = btoa(binaryString)
          
          // 在数据前添加标记，表示这是经过UTF-8编码的数据
          const markedData = "UTF8:" + base64
          
          // 加密标记后的数据
          const encrypted = encrypt.encrypt(markedData)
          
          if (!encrypted) {
            throw new Error("JSEncrypt加密失败，可能是数据太长或公钥格式错误")
          }
          
          return encrypted
        } catch (error) {
          console.error("JSEncrypt加密错误:", error)
          throw new Error("加密失败: " + error.message)
        }
      }
      
      // 使用Forge库进行RSA-OAEP加密
      function encryptWithForge(publicKey, data) {
        try {
          // 解析PEM格式的公钥
          const publicKeyObj = forge.pki.publicKeyFromPem(publicKey)
          
          // 对于中文等非ASCII字符，确保正确编码
          let dataToEncrypt = data
          
          // 使用OAEP填充加密
          let encrypted
          try {
            // 直接尝试加密原始数据
            encrypted = publicKeyObj.encrypt(dataToEncrypt, 'RSA-OAEP')
          } catch (directError) {
            console.error("直接加密失败:", directError)
            
            // 如果直接加密失败，尝试先转为UTF-8字节，再编码为Base64
            try {
              const encoder = new TextEncoder()
              const bytes = encoder.encode(dataToEncrypt)
              const base64 = btoa(String.fromCharCode.apply(null, bytes))
              
              // 在数据前添加标记，表示这是经过编码的
              dataToEncrypt = "UTF8:" + base64
              encrypted = publicKeyObj.encrypt(dataToEncrypt, 'RSA-OAEP')
            } catch (encodeError) {
              console.error("编码后加密失败:", encodeError)
              throw new Error("加密失败，即使使用编码也无法处理")
            }
          }
          
          // 转换为Base64编码
          const base64 = forge.util.encode64(encrypted)
          
          return base64
        } catch (error) {
          console.error("Forge加密错误:", error)
          throw new Error("Forge加密失败: " + error.message)
        }
      }

      async function sendToServer() {
        const encryptedData = document.getElementById("encryptedData").value

        if (!encryptedData) {
          showMessage("请先加密数据", true)
          return
        }

        document.getElementById("loadingSend").classList.remove("hide")
        try {
          const response = await fetch("/api/decrypt", {
            method: "POST",
            headers: {
              "Content-Type": "application/json"
            },
            body: JSON.stringify({ encryptedData })
          })

          const result = await response.json()

          if (result.success) {
            showMessage(`解密成功!\n原始数据: ${result.originalData}`, false)
          } else {
            showMessage("服务器解密失败: " + result.error, true)
          }
        } catch (error) {
          showMessage("请求失败: " + error.message, true)
        } finally {
          document.getElementById("loadingSend").classList.add("hide")
        }
      }

      function showMessage(message, isError) {
        const resultElement = document.getElementById("result")
        resultElement.textContent = message
        resultElement.classList.remove("hide")

        if (isError) {
          resultElement.classList.add("error")
        } else {
          resultElement.classList.remove("error")
        }
      }

      // 事件监听
      document
        .getElementById("fetchPublicKeyBtn")
        .addEventListener("click", fetchPublicKey)
      document.getElementById("encryptBtn").addEventListener("click", encryptData)
      document.getElementById("sendToServerBtn").addEventListener("click", sendToServer)

      // 页面加载时自动获取公钥
      window.addEventListener("DOMContentLoaded", fetchPublicKey)
    </script>
  </body>
</html>
